/*-------------------------------------------------------------------------
 *
 * win32security.c
 *      Microsoft Windows Win32 Security Support Functions
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *      src/port/win32security.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif


/*
 * Utility wrapper for frontend and backend when reporting an error
 * message.
 */
static
pg_attribute_printf(1, 2)
void
log_error(const char *fmt,...)
{
    va_list        ap;

    va_start(ap, fmt);
#ifndef FRONTEND
    write_stderr(fmt, ap);
#else
    fprintf(stderr, fmt, ap);
#endif
    va_end(ap);
}

/*
 * Returns nonzero if the current user has administrative privileges,
 * or zero if not.
 *
 * Note: this cannot use ereport() because it's called too early during
 * startup.
 */
int
pgwin32_is_admin(void)
{
    PSID        AdministratorsSid;
    PSID        PowerUsersSid;
    SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
    BOOL        IsAdministrators;
    BOOL        IsPowerUsers;

    if (!AllocateAndInitializeSid(&NtAuthority, 2,
                                  SECURITY_BUILTIN_DOMAIN_RID,
                                  DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0,
                                  0, &AdministratorsSid))
    {
        log_error(_("could not get SID for Administrators group: error code %lu\n"),
                  GetLastError());
        exit(1);
    }

    if (!AllocateAndInitializeSid(&NtAuthority, 2,
                                  SECURITY_BUILTIN_DOMAIN_RID,
                                  DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0,
                                  0, &PowerUsersSid))
    {
        log_error(_("could not get SID for PowerUsers group: error code %lu\n"),
                  GetLastError());
        exit(1);
    }

    if (!CheckTokenMembership(NULL, AdministratorsSid, &IsAdministrators) ||
        !CheckTokenMembership(NULL, PowerUsersSid, &IsPowerUsers))
    {
        log_error(_("could not check access token membership: error code %lu\n"),
                  GetLastError());
        exit(1);
    }

    FreeSid(AdministratorsSid);
    FreeSid(PowerUsersSid);

    if (IsAdministrators || IsPowerUsers)
        return 1;
    else
        return 0;
}

/*
 * We consider ourselves running as a service if one of the following is
 * true:
 *
 * 1) We are running as LocalSystem (only used by services)
 * 2) Our token contains SECURITY_SERVICE_RID (automatically added to the
 *      process token by the SCM when starting a service)
 *
 * The check for LocalSystem is needed, because surprisingly, if a service
 * is running as LocalSystem, it does not have SECURITY_SERVICE_RID in its
 * process token.
 *
 * Return values:
 *     0 = Not service
 *     1 = Service
 *    -1 = Error
 *
 * Note: we can't report errors via either ereport (we're called too early
 * in the backend) or write_stderr (because that calls this).  We are
 * therefore reduced to writing directly on stderr, which sucks, but we
 * have few alternatives.
 */
int
pgwin32_is_service(void)
{// #lizard forgives
    static int    _is_service = -1;
    BOOL        IsMember;
    PSID        ServiceSid;
    PSID        LocalSystemSid;
    SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};

    /* Only check the first time */
    if (_is_service != -1)
        return _is_service;

    /* First check for LocalSystem */
    if (!AllocateAndInitializeSid(&NtAuthority, 1,
                                  SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0,
                                  &LocalSystemSid))
    {
        fprintf(stderr, "could not get SID for local system account\n");
        return -1;
    }

    if (!CheckTokenMembership(NULL, LocalSystemSid, &IsMember))
    {
        fprintf(stderr, "could not check access token membership: error code %lu\n",
                GetLastError());
        FreeSid(LocalSystemSid);
        return -1;
    }
    FreeSid(LocalSystemSid);

    if (IsMember)
    {
        _is_service = 1;
        return _is_service;
    }

    /* Check for service group membership */
    if (!AllocateAndInitializeSid(&NtAuthority, 1,
                                  SECURITY_SERVICE_RID, 0, 0, 0, 0, 0, 0, 0,
                                  &ServiceSid))
    {
        fprintf(stderr, "could not get SID for service group: error code %lu\n",
                GetLastError());
        return -1;
    }

    if (!CheckTokenMembership(NULL, ServiceSid, &IsMember))
    {
        fprintf(stderr, "could not check access token membership: error code %lu\n",
                GetLastError());
        FreeSid(ServiceSid);
        return -1;
    }
    FreeSid(ServiceSid);

    if (IsMember)
        _is_service = 1;
    else
        _is_service = 0;

    return _is_service;
}
